package com.atguigu.gmall.order.service.impl;

import com.atguigu.gmall.cart.bean.CartInfo;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.common.exception.GmallAsyncException;
import com.atguigu.gmall.common.utils.GmallConsts;
import com.atguigu.gmall.oms.vo.OrderConfirmVo;
import com.atguigu.gmall.oms.vo.OrderItemVo;
import com.atguigu.gmall.oms.vo.OrderSubmitVo;
import com.atguigu.gmall.order.feign.*;
import com.atguigu.gmall.order.interceptor.UserInfoInterceptor;
import com.atguigu.gmall.order.service.OrderService;
import com.atguigu.gmall.pms.entity.SkuAttrValueEntity;
import com.atguigu.gmall.pms.entity.SkuEntity;
import com.atguigu.gmall.sms.vo.ItemSaleVo;
import com.atguigu.gmall.ums.entity.UserAddressEntity;
import com.atguigu.gmall.ums.entity.UserEntity;
import com.atguigu.gmall.wms.entity.WareSkuEntity;
import com.atguigu.gmall.wms.vo.StockLockedVo;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    //查询订单确认页需要的所有的数据
    /*
        1、用户的地址列表
        2、配送方式
        3、商品清单
            购物车选中的购物项列表
        4、用户积分

     */
    @Autowired
    UmsClient umsClient;
    @Autowired
    PmsClient pmsClient;
    @Autowired
    WmsClient wmsClient;
    @Autowired
    SmsClient smsClient;
    @Autowired
    CartClient cartClient;
    @Autowired
    OmsClient omsClient;
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    ThreadPoolExecutor executor;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Override
    public OrderConfirmVo confirm() {
        //创建订单确认页需要的数据模型：
        OrderConfirmVo confirmVo = new OrderConfirmVo();
        //1、获取用户id
        String userId = UserInfoInterceptor.getUserId();
        //2、查询用户的地址列表: ums服务
        CompletableFuture<Void> c1 = CompletableFuture.runAsync(() -> {
            ResponseVo<List<UserAddressEntity>> userAddressesResponseVo =
                    umsClient.queryUserAddressesByUserId(userId);
            List<UserAddressEntity> userAddressEntities = userAddressesResponseVo.getData();
            if (!CollectionUtils.isEmpty(userAddressEntities)) {
                confirmVo.setAddresses(userAddressEntities);
            }
        },executor);

        //3、配送方式： 写死
        confirmVo.setDeliveryCompany("谷粒配送");
        //4、购物车选中的购物项列表：cart
        CompletableFuture<List<CartInfo>> c2 = CompletableFuture.supplyAsync(() -> {
            ResponseVo<List<CartInfo>> checkedCartsResponseVo = cartClient.queryCheckedCarts(userId);
            List<CartInfo> cartInfos = checkedCartsResponseVo.getData();
            if (CollectionUtils.isEmpty(cartInfos)) {
                throw new RuntimeException("未选中要购买的商品");
            }
            return cartInfos;
        }, executor);
        CompletableFuture<Void> c3 = c2.thenAcceptAsync((cartInfos -> {
            List<OrderItemVo> orderItemVos = cartInfos.stream().map(cartInfo -> {
                //将每一个cartInfo转为一个OrderItemVo对象
                OrderItemVo orderItemVo = new OrderItemVo();
                String skuId = cartInfo.getSkuId();
                orderItemVo.setSkuId(skuId);
                orderItemVo.setCount(cartInfo.getCount());
                //查询商品最新的数据使用
                //wms:
                CompletableFuture<Void> c4 = CompletableFuture.runAsync(() -> {
                    ResponseVo<List<WareSkuEntity>> wareSkusResponseVo = wmsClient.queryWareSkusBySkuId(skuId);
                    if (!CollectionUtils.isEmpty(wareSkusResponseVo.getData())) {
                        orderItemVo.setStore(wareSkusResponseVo.getData()
                                .stream().anyMatch(wareSkuEntity -> wareSkuEntity.getStock() -
                                        wareSkuEntity.getStockLocked() - cartInfo.getCount() >= 0));
                    }
                }, executor);
                //sms
                CompletableFuture<Void> c5 = CompletableFuture.runAsync(() -> {
                    List<ItemSaleVo> itemSaleVos = smsClient.querySalesBySkuId(skuId).getData();
                    if (!CollectionUtils.isEmpty(itemSaleVos)) {
                        orderItemVo.setSales(itemSaleVos);
                    }
                }, executor);

                //pms
                CompletableFuture<Void> c6 = CompletableFuture.runAsync(() -> {
                    List<SkuAttrValueEntity> skuAttrValueEntities = pmsClient.querySkuSearchAttrValues(Long.parseLong(skuId)).getData();
                    if (!CollectionUtils.isEmpty(skuAttrValueEntities)) {
                        orderItemVo.setSaleAttrs(skuAttrValueEntities);
                    }
                }, executor);
                CompletableFuture<Void> c7 = CompletableFuture.runAsync(() -> {
                    SkuEntity skuEntity = pmsClient.querySkuById(Long.parseLong(skuId)).getData();
                    if (skuEntity != null) {
                        // BigDecimal计算解决精度问题时  需要使用它的字符串类型的构造器
                        orderItemVo.setWeight(new BigDecimal(skuEntity.getWeight() + ""));
                        orderItemVo.setPrice(skuEntity.getPrice());
                        orderItemVo.setTitle(skuEntity.getTitle());
                        orderItemVo.setDefaultImage(skuEntity.getDefaultImage());
                    }
                }, executor);
                CompletableFuture.allOf(c4,c5,c6,c7).join();
                return orderItemVo;
            }).collect(Collectors.toList());
            // 是否有货 wms
            confirmVo.setOrderItems(orderItemVos);
        }), executor);

        //5、用户积分： ums
        CompletableFuture<Void> c8 = CompletableFuture.runAsync(() -> {
            UserEntity userEntity = umsClient.queryUserById(Long.parseLong(userId)).getData();
            if (userEntity != null) {
                confirmVo.setBounds(userEntity.getIntegration());
            }
        }, executor);
        //6、生成orderToken ：保证幂等性 避免表单重复提交
        CompletableFuture<Void> c9 = CompletableFuture.runAsync(() -> {
            String timeId = IdWorker.getTimeId();
            confirmVo.setOrderToken(timeId);
            //服务器内存中保存一份：
            redisTemplate.opsForValue().set( GmallConsts.ORDER_TOKEN_PREFIX + userId, timeId, 2, TimeUnit.HOURS);
        }, executor);
        CompletableFuture.allOf(c1,c2,c3,c8,c9).join();
        //封装为页面需要展示对象
        return confirmVo;
    }
    //创建订单
    @Override
    public String createOrder(OrderSubmitVo orderSubmitVo) {
        //1、重复提交表单验证
        //获取客户端提交的token
        String orderToken = orderSubmitVo.getOrderToken();
        //获取redis中的token：
        String userId = UserInfoInterceptor.getUserId();
        /*
            我们希望获取redistoken和删除token具有原子性
            可以使用LUA脚本实现

            如果redis中的token值和 请求提交的token一样 删除redis的缓存 返回1
            如果redistoken的值不存在或者  和提交的token不一样  返回0；
         */
        String script = "if redis.call('get' , KEYS[1]) == ARGV[1] " +
                "  then return redis.call('del' , KEYS[1]) " +
                "  else return 0 end";
        log.info("script: {}" , script);
        Boolean flag = redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class),
                Arrays.asList(GmallConsts.ORDER_TOKEN_PREFIX + userId), orderToken);
        if(!flag){
            throw new GmallAsyncException("请勿重复提交表单");
        }
        //2、验价：
        //查询数据库最新的价格 和前端页面提交请求的价格对比 如果不一样 页面数据失效
        //遍历所有的商品清单：购物项列表   查询每一个购物项商品的价格*数量 累加
        List<OrderItemVo> items = orderSubmitVo.getItems();
        if(CollectionUtils.isEmpty(items)){
            throw new GmallAsyncException("没有选中商品");
        }
        // 得到items对应的所有的sku的总价格*数量累加的结果
        BigDecimal currentTotalPrice = items.stream().map(orderItemVo -> {
            //遍历每一个商品
            Integer count = orderItemVo.getCount();
            String skuId = orderItemVo.getSkuId();
            ResponseVo<SkuEntity> skuEntityResponseVo = pmsClient.querySkuById(Long.parseLong(skuId));
            SkuEntity skuEntity = skuEntityResponseVo.getData();
            if (skuEntity != null) {
                return skuEntity.getPrice().multiply(new BigDecimal(count + ""));
            }
            return new BigDecimal("0");
        }).reduce((a, b) -> a.add(b)).get();
        BigDecimal totalPrice = orderSubmitVo.getTotalPrice();
        if(currentTotalPrice.compareTo(totalPrice)!=0){
            //页面金额和数据库金额不一致
            throw new GmallAsyncException("页面数据过期，请刷新页面");
        }
        //3、验库存锁库存：修改库存锁定的数量+count(如果 锁定库存+count<=总库存 锁定成功)
        //List<OrderItemVo> items :每一个OrderItemVo对应唯一一个sku
        // 购买的每个OrderItemVo都有数量： 如果sku库存足够，更新库存locked_stock 的数量+count，更新成功代表锁定库存成功
        //如果锁定失败，需要将锁定成功的sku的库存释放
        //自定义vo类： 传需要锁定的skuId和count列表+orderToken ， 可以接受wms服务返回的锁定成功的wareSkuId和锁定状态
        List<StockLockedVo> stockLockVos = items.stream().map(orderItemVo -> {
            StockLockedVo stockLockedVo = new StockLockedVo();
            stockLockedVo.setSkuId(orderItemVo.getSkuId());
            stockLockedVo.setOrderToken(orderToken);
            stockLockedVo.setCount(orderItemVo.getCount());
            return stockLockedVo;
        }).collect(Collectors.toList());
        ResponseVo<List<StockLockedVo>> checkAndLockResponseVo = wmsClient.checkAndLock(stockLockVos);
        List<StockLockedVo> stockLockedVos = checkAndLockResponseVo.getData();
        if(CollectionUtils.isEmpty(stockLockedVos)){//锁定失败
            throw new GmallAsyncException("库存不足");
        }
        //4、创建订单保存到订单表中：   如果订单保存失败 需要释放之前锁定的库存(根据仓库Id释放库存)
        //为了保证当前订单释放库存时查找的是自己的订单的库存清单，需要指定orderToken
        //将来库存锁定成功使用orderToken为key  锁定成功的库存信息为值存到redis中，释放时使用orderToken获取
        //orderSubmitVo:代表要保存的订单信息+订单详情信息 、userId用来绑定订单给用户
        //订单数据分库分表存放，所以在保存订单和订单详情前 需要检查分库分表状态 和读写分离状态
        //  访问mycat：测试
        //  访问读写分离的两个mysql： master1和slave1  查询主从的状态  如果slave的io和sql线程都运行成功 主从没有问题
        //          salve状态有错，需要重新搭建主从(从机中停止从   重新根据主的状态配置主从 重新启动从)
        try {
            ResponseVo<String> orderTokenResponseVo = omsClient.saveOrder(userId, orderSubmitVo);
            if(StringUtils.isEmpty(orderTokenResponseVo.getData())){
                //订单创建失败
                throw new GmallAsyncException("订单创建失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
            //订单创建失败：释放第三步锁定的库存
            //mq: 发送释放库存的消息   wms消费消息释放库存
            rabbitTemplate.convertAndSend("order.exchange",
                    "stock.unlock" , orderToken);
            throw new GmallAsyncException("库存不足");
        }
        //此行执行时，代表订单创建成功
        //5、删除购物车中选中的购物项： mq发送消息：删除本次创建订单时的购物车中选中的购物项 cart服务
        //准备删除购物车选中购物项的参数：  userId(可以查找该用户的购物车)  skuIds(可以在购物车中挑选出选中的购物项)
        Map<String,Object> map = new HashMap<>();
        map.put("userId",userId);
        map.put("skuIds" , items.stream().map(orderItemVo -> orderItemVo.getSkuId())
            .collect(Collectors.toList()));
        rabbitTemplate.convertAndSend("order.exchange",
                "cart.item.delete" , map);
/*
                    value = @Queue(value = "cart.item.queue"),
                    exchange = @Exchange(value = "order.exchange" ,
                            type = ExchangeTypes.TOPIC , ignoreDeclarationExceptions = "true"),
                    key = "cart.item.delete"
 */
        //6、返回创建成功的订单token：前端跳转到待支付页面
        return orderToken;
    }
}
